package com.huslee.shark.log.log4j2.plugins;

import com.fasterxml.jackson.core.PrettyPrinter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.core.util.MinimalPrettyPrinter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.huslee.shark.log.log4j2.event.ElkLog4jEvent;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.*;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.jackson.JsonConstants;
import org.apache.logging.log4j.core.jackson.Log4jJsonObjectMapper;
import org.apache.logging.log4j.core.layout.*;
import org.apache.logging.log4j.core.util.StringBuilderWriter;
import org.apache.logging.log4j.util.Strings;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Set;

/**
 * @author: zhanglin574@xdf.cn
 * @date: 2021/8/9
 * @description:
 */
@Plugin(name="ElkSonLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE,printObject = true)
public final class ElkJsonLayout extends AbstractElkJacksonLayout {
    protected static final String DEFAULT_EOL = "\r\n";
    protected static final String DEFAULT_PATTERN = "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %t %c - %m%n";
    private String projectName ;
    private PatternLayout patternLayout;
    private boolean useJson;
    private static final String DEFAULT_FOOTER = "]";

    private static final String DEFAULT_HEADER = "[";
    protected ElkJsonLayout(Configuration config, String eventPattern,
                            Charset charset, String headerPattern, String footerPattern,
                            boolean locationInfo, boolean properties, boolean compact,
                            boolean complete, boolean eventEol, String endOfLine,
                            boolean encodeThreadContextAsList, boolean includeStacktrace,
                            boolean stacktraceAsString, boolean objectMessageAsJsonObject,
                            boolean includeNullDelimiter, String projectName, boolean useJson) {

        super(config, new ElkJacksonFactory(encodeThreadContextAsList, includeStacktrace, stacktraceAsString, objectMessageAsJsonObject).newWriter(
                locationInfo, properties, compact),
                charset, compact, complete, eventEol, endOfLine,
                PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).build(),
                PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).build(),
                includeNullDelimiter);
        this.projectName = projectName;
        this.useJson = useJson;
        this.patternLayout = PatternLayout.newBuilder()
                .withPattern(eventPattern)
                .withConfiguration(config)
                .withCharset(charset)
                .withAlwaysWriteExceptions(true)
                .withNoConsoleNoAnsi(false)
                .withHeader(headerPattern)
                .withFooter(footerPattern)
                .build();
    }
    @Override
    public String toSerializable(LogEvent event) {
        if(useJson){
            return writeJson(event);
        }
        String message = patternLayout.toSerializable(event);
        if(event instanceof ElkLog4jEvent){
            StringBuilder builder = new StringBuilder();
            int len = message.length();
            int segmentIndex = 0;
            boolean notBlank = true;
            char blank = ' ';
            for(int i = 0; i < len; i++){
                char c = message.charAt(i);
                if(c == blank){
                    if (notBlank){
                        segmentIndex += 1;
                        notBlank = false;
                        if(segmentIndex == 5){
                            builder.append(" TID: " + ((ElkLog4jEvent)event).getSkyWalkingDynamicField());
                        }
                    }
                }else{
                    notBlank = true;
                }
                builder.append(c);
            }
            message = builder.toString();
        }
        return message;
    }

    /**
     * 转换为json格式日志
     * @param event
     * @return
     */
    private String writeJson(LogEvent event){
        final StringBuilderWriter writer = new StringBuilderWriter();
        try {
            if (complete && eventCount > 0) {
                writer.append(", ");
            }
            ElkLog4jEvent elkLog4jEvent = null;
            if(event instanceof ElkLog4jEvent){
                elkLog4jEvent = (ElkLog4jEvent)event;
            }else{
                elkLog4jEvent = new ElkLog4jEvent(event);
            }
            elkLog4jEvent.setProjectName(projectName);
            objectWriter.writeValue(writer, elkLog4jEvent);
            writer.write(eol);
            if (includeNullDelimiter) {
                writer.write('\0');
            }
            return writer.toString();
        } catch (final IOException e) {
            // Should this be an ISE or IAE?
            LOGGER.error(e);
            return Strings.EMPTY;
        }
    }

    @PluginFactory
    public static ElkJsonLayout createLayout(
            @PluginAttribute(value = "pattern", defaultString = DEFAULT_PATTERN) final String pattern,
            @PluginConfiguration final Configuration config,
            @PluginAttribute(value = "charset") final Charset charset,
            @PluginAttribute(value = "locationInfo") final boolean locationInfo,
            @PluginAttribute(value = "properties") final boolean properties,
            @PluginAttribute(value = "compact") final boolean compact,
            @PluginAttribute(value = "complete") final boolean complete,
            @PluginAttribute(value = "eventEol") final boolean eventEol,
            @PluginAttribute(value = "endOfLine") final String endOfLine,
            @PluginAttribute(value = "encodeThreadContextAsList") final boolean encodeThreadContextAsList,
            @PluginAttribute(value = "includeStacktrace" ,defaultBoolean = true) final boolean includeStacktrace,
            @PluginAttribute(value = "stacktraceAsString",defaultBoolean = false ) final boolean stacktraceAsString,
            @PluginAttribute(value = "objectMessageAsJsonObject") final boolean objectMessageAsJsonObject,
            @PluginAttribute(value = "includeNullDelimiter", defaultBoolean = false) final boolean includeNullDelimiter,
            @PluginAttribute("header") final String headerPattern,
            @PluginAttribute("footer") final String footerPattern,
            @PluginAttribute("projectName") final String projectName,
            @PluginAttribute(value = "useJson", defaultBoolean = true) final Boolean useJson) {


        return new ElkJsonLayout(config, pattern,
                charset,headerPattern, footerPattern,
                locationInfo ,properties,compact,
                complete, eventEol,endOfLine,
                encodeThreadContextAsList, includeStacktrace,
                stacktraceAsString, objectMessageAsJsonObject,
                includeNullDelimiter, projectName, useJson);
    }


    static class ElkJacksonFactory{
        private boolean encodeThreadContextAsList;
        private boolean includeStacktrace;
        private boolean stacktraceAsString;
        private boolean objectMessageAsJsonObject;
        public ElkJacksonFactory(boolean encodeThreadContextAsList,boolean includeStacktrace, boolean stacktraceAsString, boolean objectMessageAsJsonObject){
            this.encodeThreadContextAsList = encodeThreadContextAsList;
            this.includeStacktrace = includeStacktrace;
            this.stacktraceAsString = stacktraceAsString;
            this.objectMessageAsJsonObject = objectMessageAsJsonObject;
        }
        private ObjectWriter newWriter(final boolean locationInfo, final boolean properties, final boolean compact) {
            final SimpleFilterProvider filters = new SimpleFilterProvider();
            final Set<String> except = new HashSet<>(2);
            if (!locationInfo) {
                except.add(this.getPropertNameForSource());
            }
            if (!properties) {
                except.add(this.getPropertNameForContextMap());
            }
            except.add(this.getPropertNameForNanoTime());
            filters.addFilter(Log4jLogEvent.class.getName(), SimpleBeanPropertyFilter.serializeAllExcept(except));
            filters.addFilter(ElkLog4jEvent.class.getName(), SimpleBeanPropertyFilter.serializeAllExcept(except));
            final ObjectWriter writer = this.newObjectMapper().writer(compact ? this.newCompactPrinter() : this.newPrettyPrinter());
            return writer.with(filters);
        }

        protected ObjectMapper newObjectMapper() {
            return new Log4jJsonObjectMapper(encodeThreadContextAsList, includeStacktrace, stacktraceAsString, objectMessageAsJsonObject);
        }
        protected String getPropertNameForContextMap() {
            return JsonConstants.ELT_CONTEXT_MAP;
        }

        protected String getPropertNameForSource() {
            return JsonConstants.ELT_SOURCE;
        }

        protected PrettyPrinter newCompactPrinter() {
            return new MinimalPrettyPrinter();
        }

        protected PrettyPrinter newPrettyPrinter() {
            return new DefaultPrettyPrinter();
        }

        protected String getPropertNameForNanoTime() {
            return JsonConstants.ELT_NANO_TIME;
        }
    }
}
